<!DOCTYPE html>
<html>
  <head>
    <!-- [[! Document Settings ]] -->
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />

    <!-- [[! Page Meta ]] -->
    <title>[译] Android 架构：Part 1 —— 那些年我们犯过的错</title>
    <meta name="description" content="在编码 - " />

    <meta name="HandheldFriendly" content="True" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <link rel="shortcut icon" href="/assets/images/favicon.ico" >

    <!-- [[! Styles'n'Scripts ]] -->
    <link rel="stylesheet" type="text/css" href="/assets/css/screen.css" />
    <link rel="stylesheet" type="text/css"
      href="//fonts.googleapis.com/css?family=Merriweather:300,700,700italic,300italic|Open+Sans:700,400" />

    <link rel="stylesheet" type="text/css" href="/assets/css/prism.css" />

    <!-- [[! Ghost outputs important style and meta data with this tag ]] -->
    <link rel="canonical" href="/" />
<meta name="referrer" content="origin" />
<link rel="next" href="/page2/" />

<meta property="og:site_name" content="在编码" />
<meta property="og:type" content="website" />
<meta property="og:title" content="在编码" />
<meta property="og:description" content="" />
<meta property="og:url" content="/" />
<meta property="og:image" content="/assets/images/cover1.jpg" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="在编码" />
<meta name="twitter:description" content="" />
<meta name="twitter:url" content="/" />
<meta name="twitter:image:src" content="/assets/images/cover1.jpg" />

<script type="application/ld+json">
  {
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "Finding The Way Home",
  "url": "/",
  "image": "/assets/images/cover1.jpg",
  "description": ""
  }
</script>

<meta name="generator" content="Jekyll 3.0.0" />
<link rel="alternate" type="application/rss+xml" title="在编码" href="/feed.xml" />


  </head>
  <body class="home-template nav-closed">

    <div class="nav">
  <h3 class="nav-title">Home</h3>
  <a href="#" class="nav-close">
    <span class="hidden">Close</span>
  </a>
  <ul>
    <li class="nav-home " role="presentation"><a href="/">Home</a></li>
    <!-- <li class="nav-about " role="presentation"><a href="/about">About</a></li>
    <li class="nav-author " role="presentation"><a href="/author/casper">Author</a></li> -->
    <li class="nav-ios " role="presentation"><a href="/tag/iOS">iOS</a></li>
    <li class="nav-mvc " role="presentation"><a href="/tag/Android">Android</a></li>
    <li class="nav-server " role="presentation"><a href="/tag/Server">Server</a></li>
  </ul>
  <a class="subscribe-button icon-feed" href="/feed.xml">Subscribe</a>
</div>
<span class="nav-cover"></span>


    <div class="site-wrapper">

      <!-- [[! Everything else gets inserted here ]] -->
      <!-- < default -->

<!-- The comment above "< default" means - insert everything in this file into -->
<!-- the [body] of the default.hbs template, which contains our header/footer. -->

<!-- Everything inside the #post tags pulls data from the post -->
<!-- #post -->

<header class="main-header post-head no-cover">
  <nav class="main-nav overlay clearfix">
      <a class="back-button icon-arrow-left" href='/'>Home</a>
    <a class="menu-button icon-menu" href="#"><span class="word">Menu</span></a>
  </nav>
</header>

<main class="content" role="main">

  <article class="post">

    <header class="post-header">
      <h1 class="post-title">[译] Android 架构：Part 1 —— 那些年我们犯过的错</h1>
      <section class="post-meta">
        <a href='/'>Listen</a>
        <time class="post-date" datetime="2017-07-26">26 Jul 2017</time>
        <!-- [[tags prefix=" on "]] -->
        
        on
        
        
        <a href='/tag/Android'>Android</a>,
        
        
        
        <a href='/tag/Clean Architecture'>Clean Architecture</a>
        
        
        
      </section>
    </header>

    <section class="post-content">

      <p>本系列文章旨在概述我们搭建 Android 应用程序架构时可能会碰到的问题。我意识到，无论实现 Android app 架构的过程多么困难，结果证明这些一定是完成每一个卓越的应用的基础。</p>

<p>每种技术都有其<strong>自然的进化</strong>。或者更确切地说，它的社区经历了进化的过程。一个新的计算机语言或框架的早期采用者是爱好者，他们只是希望掌握技术，并尽快完成一些工作。通常，新社区规模小，在开发人员之间的知识传递潜力有限，也就是说，每个人都从自己的错误中学习，因为没有架构指南可用。</p>

<p><img src="../images/android/Illustration_1.png" alt=""></p>

<h2>早期 Android 们的痛点：谷歌是否关心？</h2>

<p>你可以说，有很多资深的家伙在不同的技术上有很多的经验，但谁也没有时间提出标准。嗯，不一定。如果技术背后有一个强大的公司指望赚钱，他们的布道师会告诉你这个新语言多么酷，可以做很多事情，容易学习，可扩展，并且可以满足数以百万计的用户。</p>

<p>微软就经常用它的技术干这样的事情。另一方面，当谷歌收购了 Android， 我真的以为他们只是把它当作一个无关紧要的项目。 如果你从 Android 诞生之时就进入 Android 的世界，你一定记得 Google 根本不在乎你的那种沮丧感。 那些有额外的经验，能力和帮助社区的意愿的少数几个人现在是 Android 的超级明星或大神 —— 譬如 Jake Wharton。</p>

<blockquote>
<p>“When Google bought Android, I honestly think they treated it just as some other side project.”</p>
</blockquote>

<p><img src="../images/android/xAmvxi.gif" alt=""></p>

<p>可能你会说，你不必考虑太多架构和组织代码的事情，因为（Android）Framework 帮你做了。Android 强迫你把界面放到 Activity 中，可重用的界面放到 Fragment 中， 后台服务放到 Service 中，并且用 Broadcast Receiver 和其它组件通信，这样可以使你的生活变得更美好，是这样吗？不是的。</p>

<p>首先，有一些很好的实践和原则确实很好，与技术（平台）无关。例如，单一职责原则，依赖倒置原则，面向接口编程，杀死全局状态，尝试消灭所有状态，等等。</p>

<p>Framework 很少强迫你遵循原则。恰恰相反，它们以最坏的方式侵犯这些最佳实践和原则。想想 Context 这个你到处使用的上帝对象（God object），各种单例管理者（singleton managers），拥有生命周期的 Fragment（那是怎样的噩梦呢），常常不能正确实现的 AsyncTask，它们吸着你 app 的血。</p>

<p>一个缺乏指导的刚入行的开发者很容易造出一个怪物而不是一个 app。把它想象成一个意大利面条般的怪物吧 —— 这是一个不错的怪物，但不是一个好的 app。</p>

<blockquote>
<p>译者注：意大利面条般的怪物寓指一团乱麻的代码</p>
</blockquote>

<p><img src="../images/android/Illustration_2.png" alt=""></p>

<p>最后，技术（technology）和 Framework 隐藏了 app 的用途。好的，这是一个 Android app，但是一个什么样的 Android app 呢？新闻阅读器？音乐播放器？ 语言学习程式？天气应用？也许是一个计划表。如果所有的东西都打包到由 Framework 提供的类，你就说不出（这是什么 app）。</p>

<p>正如 Robert Martin，也就是 Uncle Bob 说，“你的架构应该尖声喊出（scream）app 是做什么”，更技术一点说，业务逻辑应该清晰地分离，独立于 Framework。</p>

<h2>Android 架构的四条黄金法则</h2>

<p>我希望已经说清楚了，你不应当依赖 Framework 来使你的代码整洁有序，尤其是在编写 Android 代码的时候。我们很早以前就意识到这点，可是缺乏拿出牛逼解决方案的经验。架构失败要花很多时间才能表现出来，又不可以在项目中途改变整个架构。也不可能有时间将旧项目重构成新的、酷的、（想成为）最佳的架构。因此，我们采取渐进的方法，慢慢地从一个项目到另一个项目搭建我们的架构，从我们的错误中学习。我们认为我们的架构应该满足几个目标，它们是检验我们的方法的标准。好的架构应该做到:</p>

<ol>
<li>满足众多利益相关者的需求</li>
<li>支持关注点分离</li>
<li>逃离现实世界（Android、DB、Internet...）</li>
<li>使你的组件可测试</li>
</ol>

<h3>I.满足众多利益相关者</h3>

<p>利益相关者（在这篇文章中）是任何对你的 app 开发感兴趣的人。除了开发，还有视觉设计师，交互设计师，项目经理，数据库管理员，测试等等。</p>

<p>当然，你不可能以某种方式组织你的代码，例如，交互设计师可以打开项目并立即了解所有内容，甚至可以进行一些修改。It&#39;s a unicorn.</p>

<p><img src="../images/android/Illustration_3.png" alt=""></p>

<p>我的意思是，你<strong>可以</strong>以这样一种方式组织你的代码，那个和交互设计师对接的程序员只需要打理和交互相关的代码。因此，所有交互被分离到那些负责交互的 classes / modules / components / whatever （组件）中，当处理 app 的交互部分时，只需要打理那些组件。</p>

<blockquote>
<p>译者注：如果暂时不能理解利益相关者，没关系的，看完本系列第三部分你就明白了</p>
</blockquote>

<h3>II.支持关注点分离</h3>

<p>我刚刚所说的就是一个关注点分离的例子。我们支持这种特定的方法，因为它能很好地表达团队组织和项目阶段的配合，一般来说，你的架构也应该支持关注点分离。关注点分离或者单一职责原则是指，每一个组件应该只有一个变化的原因。</p>

<h3>III.逃离真实世界（Android、DB、Internet...）</h3>

<p>逃离真实世界这条规则，先前已经提及。我们曾说我们想要尖声喊出 app 真正是做什么的，就是那样。我们想要强调业务逻辑并且隐藏 Framework 的细节。这条规则应该更严格：不仅要隐藏 Framework 的细节，而且要隐藏所有与外部世界相关的细节。</p>

<p><img src="../images/android/Illustration_4.png" alt=""></p>

<p>所有的肮脏的 Android 的东西，如传感器、通知机制、屏幕细节、数据库访问、互联网访问等。</p>

<h3>IV.使你的组件可测试</h3>

<p>你应该尽可能地对你的 app 进行单元测试，并且你的架构应该允许你这样做。如果你不能测试所有东西，你至少应该覆盖你的业务逻辑。与真实世界分离可以很方便地做到这点。如果你的业务逻辑清晰地和 app 其余部分隔离，是很容易测试的。</p>

<p><img src="../images/android/giphy-5.gif" alt=""></p>

<h2>第一次迭代 —— 上帝 Activity</h2>
<figure class="highlight"><pre><code class="language-java" data-lang="java">public final class UsersActivity extends ListActivity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       //...
       new ListUsers().execute();
   }

   private final class ListUsers extends AsyncTask&lt;Void, Void, Void&gt; {

       @Override
       protected Void doInBackground(Void... voids) {
           // final SQLiteOpenHelper sqLiteOpenHelper = ...
           // JsonObjectRequest jsObjRequest = new JsonObjectRequest
           // (Request.Method.GET, url, null, new Response.Listener&lt;JSONObject&gt;() {
           // MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);
           // showData(user);
           return null;
       }
   }
}
</code></pre></figure>
<p>你可能在 “上古时代” 看到过这样的代码。如果没有，说明你很年轻。这是怎么回事？<strong>一切！</strong></p>

<p><img src="../images/android/no.gif" alt=""></p>

<p>我们有一个 Activity 操作数据库，访问网络，解析数据，切换线程以及渲染数据。所有的利益相关者都在看这一个类，没有关注点是分离的，它是不可测试的，业务逻辑和 Android 的东西混杂在一起。</p>

<p><img src="../images/android/Diagram_1-1.png" alt=""></p>

<blockquote>
<p>译者注：留意上图左边红色的标签。每个标签分别对应一条黄金法则，红色表示不满足。SRP 是指单一职责原则，即分离关注点。</p>
</blockquote>

<h2>第二次迭代 —— MVP</h2>

<p>第一种方法显然是不能工作的。我们尝试过的第一件事情是 MVP，或者说 model-view-presenter。每个人都熟悉 MVP。它是最受欢迎的架构模式之一。看起来像这样：</p>

<p><img src="../images/android/Diagram_2-1.png" alt=""></p>

<p>这里，我们分离了实际上是 Android Fragment 的 View，我们拥有代表我们业务的（领域）模型，最后我们有协调一切的 Presenter。这肯定是更好的。关注点有了一些分离，利益相关者不再那么困惑，你也可以写一些单元测试了。尽管如此，由于 Presenter 直接操作数据库和所有一切，我们仍然和真实世界混杂在一起。Presenter 成了上帝对象。它处理模型，将数据发送到视图，它拥有业务逻辑（业务逻辑是那些齿轮 :)），它访问数据库和网络，获取传感器数据，等等。所以，是好了些，但可以更好。</p>

<p><img src="../images/android/Diagram_3-3.png" alt=""></p>

<blockquote>
<p>译者注：黄色的标签表示好了些</p>
</blockquote>

<h2>第三次迭代 —— MVP + managers</h2>

<p>当政府不知道做什么的时候它会做什么？它成立一个代理机构。当开发不知道做什么的时候他们会做什么？他们引入一些 Manager。你不一定把它命名为 “*Manager” 。 这些类有很多名字：uitls、helpers、fooBarBuzz-ator、等等。因此我们引入 Manager。</p>

<p><img src="../images/android/Diagram_4-1.png" alt=""></p>

<p>说实话，这甚至有点凑效。业务逻辑包含在 Manager 中。利益相关者知道往哪看，关注点一定程度是分离的但可以做得更好，你可以编写更多的测试，但你依然直接触摸 Android ，所以你必须编写 Android 测试用例，并预先填写数据库来测试业务逻辑，一个字：不爽。是的，Manager 有变成巨兽的倾向，很快就变得难以维护。你可能争论说它不会变得更复杂，你可以通过更简单的架构来更快地提供代码，但通过这种方法依然会有很多 BUG，可维护性也会遭到破坏。</p>

<p><img src="../images/android/Diagram_5-2.png" alt=""></p>

<blockquote>
<p>译者注：留意 Manager this 和 Manager that 之间的标签</p>
</blockquote>

<h2>总结</h2>

<p>在本系列的第一部分，我们经历了搭建实际可用的 Android 架构的挑战。良好的 Android 架构应该满足众多利益相关者的需求，支持关注点分离，强调业务逻辑，隐藏 Framework 的细节，并使你所有的组件都可以测试。在系列的第二部分，我们将向你展示我们如何管理对我们有用的功能。在此之前，你是否有如何创建合适的 Android 工作流的建议？或者你遇到了什么问题？</p>

<p><a href="http://five.agency/android-architecture-part-1-every-new-beginning-is-hard/">原文</a></p>

<p><a href="./android-architecture-part-2-clean-architecture.html">阅读第二部分</a></p>


    </section>

    <footer class="post-footer">

      <!-- Everything inside the #author tags pulls data from the author -->
      <!-- #author-->

      
      <figure class="author-image">
        <a class="img" href='/' style="background-image: url(/assets/images/me.jpg)"><span class="hidden">'s Picture</span></a>
      </figure>
      

      <section class="author">
        <h4><a href='/'>Listen</a></h4>

        
        <p> 移动开发工程师，熟悉 Android、iOS、ReactNative</p>
        
        <div class="author-meta">
          <span class="author-location icon-location"> 深圳</span>
          <span class="author-link icon-link"><a href="https://github.com/listenzz"> Github</a></span>
        </div>
      </section>

      <!-- /author  -->

      <section class="share">
        <h4>Share this post</h4>
        <a class="icon-twitter" href="http://twitter.com/share?text=[译] Android 架构：Part 1 —— 那些年我们犯过的错&amp;url=https://github.com/listenzzandroid-architecture-part-1-every-new-beginning-is-hard.html"
          onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
          <span class="hidden">Twitter</span>
        </a>
        <a class="icon-facebook" href="https://www.facebook.com/sharer/sharer.php?u=https://github.com/listenzzandroid-architecture-part-1-every-new-beginning-is-hard.html"
          onclick="window.open(this.href, 'facebook-share','width=580,height=296');return false;">
          <span class="hidden">Facebook</span>
        </a>
        <a class="icon-google-plus" href="https://plus.google.com/share?url=https://github.com/listenzzandroid-architecture-part-1-every-new-beginning-is-hard.html"
          onclick="window.open(this.href, 'google-plus-share', 'width=490,height=530');return false;">
          <span class="hidden">Google+</span>
        </a>
      </section>

      <!-- Add Disqus Comments -->
      <!-- <section class="disqus">
    <div id="disqus_thread"></div>
    <script type="text/javascript">
        var disqus_shortname  = 'listenzz';
        var disqus_identifier = '[译] Android 架构：Part 1 —— 那些年我们犯过的错';
        var disqus_title      = "[译] Android 架构：Part 1 —— 那些年我们犯过的错";
        
        (function() {
         var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
         dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
         (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
         })();
        </script>
    <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
    <a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</section>
 -->

    </footer>

  </article>

</main>

<aside class="read-next">

  <!-- [[! next_post ]] -->
  
  <a class="read-next-story no-cover" href="/android-architecture-part-2-clean-architecture.html">
    <section class="post">
      <h2>[译] Android 架构：Part 2 —— 介绍 Clean Architecture</h2>
      <p>在本系列的第一部分，我们介绍了我们在寻找可行架构的道路上所犯过的错误。在这部分，我们将介绍传说中的 Clean Architecture。 当你在谷歌搜索 "clean architecture" 时，你看到的第一张图片是： ![](../images/android/Graph-1.png) 它也被称为洋葱架构，因为图看起来象个洋葱（你会意识到你需要写样板代码写到哭）；或者是端口和适配器，因为你可以看到右图的一些端口。六角架构是另一个相似的架构。 Clean Architecture 是前面提到的 Uncle Bob 的心血结晶，他是...</p>
    </section>
  </a>
  
  <!-- [[! /next_post ]] -->
  <!-- [[! prev_post ]] -->
  
  <a class="read-next-story prev no-cover" href="/configue-mac-for-develop.html">
    <section class="post">
      <h2>Mac 开发环境配置</h2>
      <p>因为某些原因，比如入手了新的电脑，或者是重装系统，我们需要配置开发环境。本文讲述 Mac OS 下的开发环境配置。 Update: 2020/02/19 更新到 macOS Catalina Xcode 就算你不从事 iOS 开发，也请确保你已经安装了 Xcode，并至少打开过一次。因为很多工具的安装需要依赖 Xcode。...</p>
    </section>
  </a>
  
  <!-- [[! /prev_post ]] -->
</aside>

<!-- /post -->


      <footer class="site-footer clearfix">
        <section class="copyright"><a href="/">在编码</a> &copy; 2020</section>
        <section class="poweredby">Proudly published with <a href="https://jekyllrb.com/">Jekyll</a> using <a href="https://github.com/biomadeira/jasper">Jasper</a></section>
      </footer>
    </div>
    <!-- [[! Ghost outputs important scripts and data with this tag ]] -->
    <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
    <!-- [[! The main JavaScript file for Casper ]] -->
    <script type="text/javascript" src="/assets/js/jquery.fitvids.js"></script>
    <script type="text/javascript" src="/assets/js/index.js"></script>
    <script type="text/javascript" src="/assets/js/prism.js"></script>

    <!-- Add Google Analytics  -->
    <!-- Google Analytics Tracking code -->
<script>
 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
   (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
   m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
 })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

 ga('create', 'UA-96120107-1', 'auto');
 ga('send', 'pageview');

</script>

  </body>
</html>
